/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.analysis;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.solr.common.ResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.util.plugin.ResourceLoaderAware;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Factory for {@link SlowSynonymFilter} (only used with luceneMatchVersion <
 * 3.4)
 * 
 * <pre class="prettyprint" >
 * &lt;fieldType name="text_synonym" class="solr.TextField" positionIncrementGap="100"&gt;
 *   &lt;analyzer&gt;
 *     &lt;tokenizer class="solr.WhitespaceTokenizerFactory"/&gt;
 *     &lt;filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="false"
 *             expand="true" tokenizerFactory="solr.WhitespaceTokenizerFactory"/&gt;
 *   &lt;/analyzer&gt;
 * &lt;/fieldType&gt;
 * </pre>
 * 
 * @deprecated (3.4) use {@link SynonymFilterFactory} instead. only for precise
 *             index backwards compatibility. this factory will be removed in
 *             Lucene 5.0
 */
@Deprecated
final class SlowSynonymFilterFactory extends BaseTokenFilterFactory implements
		ResourceLoaderAware {

	public void inform(ResourceLoader loader) {
		String synonyms = args.get("synonyms");
		if (synonyms == null)
			throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
					"Missing required argument 'synonyms'.");
		boolean ignoreCase = getBoolean("ignoreCase", false);
		boolean expand = getBoolean("expand", true);

		String tf = args.get("tokenizerFactory");
		TokenizerFactory tokFactory = null;
		if (tf != null) {
			tokFactory = loadTokenizerFactory(loader, tf, args);
		}

		Iterable<String> wlist = loadRules(synonyms, loader);

		synMap = new SlowSynonymMap(ignoreCase);
		parseRules(wlist, synMap, "=>", ",", expand, tokFactory);
	}

	/**
	 * @return a list of all rules
	 */
	protected Iterable<String> loadRules(String synonyms, ResourceLoader loader) {
		List<String> wlist = null;
		try {
			File synonymFile = new File(synonyms);
			if (synonymFile.exists()) {
				wlist = loader.getLines(synonyms);
			} else {
				List<String> files = StrUtils.splitFileNames(synonyms);
				wlist = new ArrayList<String>();
				for (String file : files) {
					List<String> lines = loader.getLines(file.trim());
					wlist.addAll(lines);
				}
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return wlist;
	}

	private SlowSynonymMap synMap;

	static void parseRules(Iterable<String> rules, SlowSynonymMap map,
			String mappingSep, String synSep, boolean expansion,
			TokenizerFactory tokFactory) {
		int count = 0;
		for (String rule : rules) {
			// To use regexes, we need an expression that specifies an odd
			// number of chars.
			// This can't really be done with string.split(), and since we need
			// to
			// do unescaping at some point anyway, we wouldn't be saving any
			// effort
			// by using regexes.

			List<String> mapping = StrUtils.splitSmart(rule, mappingSep, false);

			List<List<String>> source;
			List<List<String>> target;

			if (mapping.size() > 2) {
				throw new RuntimeException("Invalid Synonym Rule:" + rule);
			} else if (mapping.size() == 2) {
				source = getSynList(mapping.get(0), synSep, tokFactory);
				target = getSynList(mapping.get(1), synSep, tokFactory);
			} else {
				source = getSynList(mapping.get(0), synSep, tokFactory);
				if (expansion) {
					// expand to all arguments
					target = source;
				} else {
					// reduce to first argument
					target = new ArrayList<List<String>>(1);
					target.add(source.get(0));
				}
			}

			boolean includeOrig = false;
			for (List<String> fromToks : source) {
				count++;
				for (List<String> toToks : target) {
					map.add(fromToks, SlowSynonymMap.makeTokens(toToks),
							includeOrig, true);
				}
			}
		}
	}

	// a , b c , d e f => [[a],[b,c],[d,e,f]]
	private static List<List<String>> getSynList(String str, String separator,
			TokenizerFactory tokFactory) {
		List<String> strList = StrUtils.splitSmart(str, separator, false);
		// now split on whitespace to get a list of token strings
		List<List<String>> synList = new ArrayList<List<String>>();
		for (String toks : strList) {
			List<String> tokList = tokFactory == null ? StrUtils.splitWS(toks,
					true) : splitByTokenizer(toks, tokFactory);
			synList.add(tokList);
		}
		return synList;
	}

	private static List<String> splitByTokenizer(String source,
			TokenizerFactory tokFactory) {
		StringReader reader = new StringReader(source);
		TokenStream ts = loadTokenizer(tokFactory, reader);
		List<String> tokList = new ArrayList<String>();
		try {
			CharTermAttribute termAtt = ts
					.addAttribute(CharTermAttribute.class);
			while (ts.incrementToken()) {
				if (termAtt.length() > 0)
					tokList.add(termAtt.toString());
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			reader.close();
		}
		return tokList;
	}

	private static TokenizerFactory loadTokenizerFactory(ResourceLoader loader,
			String cname, Map<String, String> args) {
		TokenizerFactory tokFactory = (TokenizerFactory) loader
				.newInstance(cname);
		tokFactory.init(args);
		if (tokFactory instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) tokFactory).inform(loader);
		}
		return tokFactory;
	}

	private static TokenStream loadTokenizer(TokenizerFactory tokFactory,
			Reader reader) {
		return tokFactory.create(reader);
	}

	public SlowSynonymMap getSynonymMap() {
		return synMap;
	}

	public SlowSynonymFilter create(TokenStream input) {
		return new SlowSynonymFilter(input, synMap);
	}
}
